namespace C4F.DevKit.Bluetooth.ObjectPushProfile
{
    #region Using Directives
    using System.Net.Sockets;
    using System.IO;
    using System;
    using C4F.DevKit.Bluetooth.ObjectExchange;
    using System.Threading;
    using System.Collections.Generic;
    using System.Text;
    using System.ComponentModel;
    using C4F.DevKit.Bluetooth.ObjectPushProfile.Resources;
    #endregion

    /// <summary>
    /// Implements Object Push Profile.
    /// </summary>
    public class BluetoothOPPManager : Component
    {
        #region Private Fields

        /// <summary>
        /// NetworkStream used for communicating with a remote device.
        /// </summary>
        private NetworkStream networkStream;

        /// <summary>
        /// TypeVerifier may be used for type verification.
        /// </summary>
        private ObjectTypeVerifier typeVerifier;

        /// <summary>
        /// Maximum packet size to send over the network stream.
        /// </summary>
        private ushort maxPacketSize;

        /// <summary>
        /// The queue of pending asynchronous OPP requests.
        /// </summary>
        private List<OPPRequest> requestQueue;

        /// <summary>
        /// A background worker that handles asynchronous OPP requests.
        /// </summary>
        private BackgroundWorker asyncOperationHandler;

        /// <summary>
        /// The asynchronous OPP request that is being handled by 
        /// asyncOperationHandler.
        /// </summary>
        private OPPRequest runningAsyncRequest;

        #endregion

        #region Private Constants

        /// <summary>
        /// Default maximum packet size to send over the network stream.
        /// </summary>
        private const ushort DEFAULT_MAX_PACKET_SIZE = 8192;

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the NetworkStream used for communicating with a remote device.
        /// </summary>
        [Browsable(false)]
        public NetworkStream NetworkStream
        {
            get
            { return this.networkStream; }
            set
            { this.networkStream = value; }
        }

        /// <summary>
        /// This object needs to be set in order to make use of the type 
        /// verification features. It is required that the value for this 
        /// property be set with an instance of a class deriving from the 
        /// abstract class ObjectTypeVerifier with appropriate implementation
        /// for the overrideable method VerifyType(). If the type verifier 
        /// is set, it may be used after every Get operation. This property 
        /// should be set to null if the type verifier is not to be used.
        /// </summary>
        [Browsable(false)]
        public ObjectTypeVerifier TypeVerifier
        {
            get
            { return this.typeVerifier; }
            set
            { this.typeVerifier = value; }
        }

        #endregion

        #region Events and Delegates

        /// <summary>
        /// The event handler for an ObjectPushBegin event.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">EventArgs when an Object Push operation begins.
        /// </param>
        public delegate void ObjectPushBeginEventHandler(
            object sender, ObjectPushBeginEventArgs e);

        /// <summary>
        /// This event is fired when an Object Push operation begins.
        /// </summary>
        public event ObjectPushBeginEventHandler ObjectPushBegin;

        /// <summary>
        /// The event handler for an ObjectPushCompleted event.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">EventArgs when an Object Push operation completes.
        /// </param>
        public delegate void ObjectPushCompletedEventHandler(
            object sender, ObjectPushCompletedEventArgs e);

        /// <summary>
        /// This event is fired when an Object Push operation completes.
        /// </summary>
        public event ObjectPushCompletedEventHandler ObjectPushCompleted;

        /// <summary>
        /// The event handler for a BusinessCardPullBegin event.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">EventArgs when a Business Card Pull operation 
        /// begins.</param>
        public delegate void BusinessCardPullBeginEventHandler(
            object sender, BusinessCardPullBeginEventArgs e);

        /// <summary>
        /// This event is fired when a Business Card Pull operation begins.
        /// </summary>
        public event BusinessCardPullBeginEventHandler BusinessCardPullBegin;

        /// <summary>
        /// The event handler for a BusinessCardPullCompleted event.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">EventArgs when a Business Card Pull operation 
        /// completes.</param>
        public delegate void BusinessCardPullCompletedEventHandler(
            object sender, BusinessCardPullCompletedEventArgs e);

        /// <summary>
        /// This event is fired when a Business Card Pull operation completes.
        /// </summary>
        public event BusinessCardPullCompletedEventHandler
            BusinessCardPullCompleted;

        /// <summary>
        /// The event handler for a BusinessCardExchangeBegin event.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">EventArgs when a Business Card Exchange operation
        /// begins.</param>
        public delegate void BusinessCardExchangeBeginEventHandler(
            object sender, BusinessCardExchangeBeginEventArgs e);

        /// <summary>
        /// This event is fired when a Business Card Exchange operation begins.
        /// </summary>
        public event BusinessCardExchangeBeginEventHandler
            BusinessCardExchangeBegin;

        /// <summary>
        /// The event handler for a BusinessCardExchangeCompleted event.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">EventArgs when a Business Card Exchange operation
        /// completes.</param>
        public delegate void BusinessCardExchangeCompletedEventHandler(
            object sender, BusinessCardExchangeCompletedEventArgs e);

        /// <summary>
        /// This event is fired when a Business Card Exchange operation 
        /// completes.
        /// </summary>
        public event BusinessCardExchangeCompletedEventHandler
            BusinessCardExchangeCompleted;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes the OPPManager object.
        /// </summary>
        public BluetoothOPPManager()
        {
            // Initialize request queue and background worker.
            InitializeOPPManager();
        }

        /// <summary>
        /// Initializes the OPPManager object.
        /// </summary>
        /// <param name="networkStream">Network stream over which data has to 
        /// be transferred.</param>
        /// <exception cref="ArgumentNullException">networkStream is passed as
        /// a null reference.</exception>
        public BluetoothOPPManager(NetworkStream networkStream)
        {
            if (networkStream == null)
                throw new ArgumentNullException("networkStream");
            // Set the network stream.
            this.networkStream = networkStream;

            // Initialize request queue and background worker.
            InitializeOPPManager();
        }

        #endregion

        #region Private Functions/Methods

        /// <summary>
        /// Initializes OPPManager object.
        /// </summary>
        private void InitializeOPPManager()
        {
            // Initialize the list used to maintain the pending asynchronous
            // requests.
            this.requestQueue = new List<OPPRequest>();

            // Initialize the background worker.
            this.asyncOperationHandler = new BackgroundWorker();
            this.asyncOperationHandler.DoWork +=
                new DoWorkEventHandler(asyncOperationHandler_DoWork);
            this.asyncOperationHandler.RunWorkerCompleted +=
                new RunWorkerCompletedEventHandler(
                this.asyncOperationHandler_RunWorkerCompleted);
            this.asyncOperationHandler.WorkerSupportsCancellation = true;
        }

        /// <summary>
        /// Adds an asynchronous OPP request to the Request Queue.
        /// </summary>
        /// <param name="request">The asynchronous OPP request to be added to
        /// the Request Queue.</param>
        private void AddRequestToRequestQueue(OPPRequest request)
        {
            // Add the request to the list of pending requests.
            Monitor.Enter(this.requestQueue);
            this.requestQueue.Add(request);
            Monitor.Exit(this.requestQueue);

            // If the background worker handling asynchronous operations is not
            // busy, put it to work.
            if (!this.asyncOperationHandler.IsBusy)
                this.asyncOperationHandler.RunWorkerAsync();
        }

        /// <summary>
        /// Event handler for background worker's DoWork event. This method 
        /// executes an asynchronous operation from the request queue. It also 
        /// raises the asynchronous operation's Begin if implemented.
        /// </summary>
        /// <param name="sender">The background worker that will process this 
        /// method.</param>
        /// <param name="e">DoWorkEventArgs to store the outcome of the 
        /// operation.</param>
        private void asyncOperationHandler_DoWork(
            object sender, DoWorkEventArgs e)
        {
            Monitor.Enter(this.requestQueue);
            try
            {
                if (this.requestQueue.Count != 0)
                {
                    // There are pending asynchronous requests in the request queue.
                    // Get the 1st request from the list of pending asynchronous
                    // operations.       
                    this.runningAsyncRequest = this.requestQueue[0];
                    // Remove it from the list.
                    this.requestQueue.RemoveAt(0);
                }
                else
                    // No work to be done.
                    return;
            }
            finally
            {
                Monitor.Exit(this.requestQueue);
            }

            // If the request was cancelled, set e.Cancel to true.
            if (this.asyncOperationHandler.CancellationPending)
            {
                e.Cancel = true;
                return;
            }

            // Get the parameters of the request.
            object[] requestParameters =
                this.runningAsyncRequest.RequestParameters;

            // Raise the appropriate begin event and perform the operation.
            switch (this.runningAsyncRequest.RequestType)
            {
                case OPPRequestType.ObjectPush:
                    // Raise the Object Push Begin event if required.
                    if (this.ObjectPushBegin != null)
                        ObjectPushBegin(this,
                            new ObjectPushBeginEventArgs(
                            requestParameters[0].ToString(),
                            this.runningAsyncRequest.UserState));
                    try
                    {
                        // Lock the network stream.
                        Monitor.Enter(this.networkStream);

                        // Perform the operation.
                        PushObjectToOPPService(requestParameters[0].ToString(),
                                   (byte[])requestParameters[1]);
                        // No result is to be returned.
                    }
                    catch (Exception ex)
                    {
                        e.Result = new OPPException(
                            OPPResources.Error_ObjectPush, ex);
                    }
                    finally
                    {
                        // Release the network stream.
                        Monitor.Exit(this.networkStream);
                    }
                    break;

                case OPPRequestType.BusinessCardPull:
                case OPPRequestType.BusinessCardPullWithVerification:
                    {
                        // Raise the Business Card Begin event if required.
                        if (this.BusinessCardPullBegin != null)
                            BusinessCardPullBegin(this,
                                new BusinessCardPullBeginEventArgs(
                                this.runningAsyncRequest.UserState));

                        // Perform the operation.
                        try
                        {
                            // Lock the network stream.
                            Monitor.Enter(this.networkStream);

                            string cardName;
                            byte[] cardContents;
                            PullBusinessCardFromOPPService(
                                out cardName, out cardContents,
                                requestParameters[0].ToString());
                            // Set the return parameters to e.Result.
                            e.Result = new object[] { cardName, cardContents };
                        }
                        catch (Exception ex)
                        {
                            e.Result = new OPPException(
                                OPPResources.Error_BusinessCardPull, ex);
                        }
                        finally
                        {
                            // Release the network stream.
                            Monitor.Exit(this.networkStream);
                        }
                    }
                    break;

                case OPPRequestType.BusinessCardExchange:
                case OPPRequestType.BusinessCardExchangeWithVerification:
                    {
                        // Raise the Business Card Exchange event if required.
                        if (this.BusinessCardExchangeBegin != null)
                            BusinessCardExchangeBegin(this,
                                    new BusinessCardExchangeBeginEventArgs(
                                    requestParameters[0].ToString(),
                                    this.runningAsyncRequest.UserState));

                        // Perform the operation.
                        try
                        {
                            // Lock the network stream.
                            Monitor.Enter(this.networkStream);

                            string cardName;
                            byte[] cardContents;
                            ExchangeBusinessCardsWithOPPService(
                                requestParameters[0].ToString(),
                                (byte[])requestParameters[1],
                                out cardName, out cardContents,
                                requestParameters[2].ToString());
                            // Set the return parameters to e.Result.
                            e.Result = new object[] { cardName, cardContents };
                        }
                        catch (Exception ex)
                        {
                            e.Result = new OPPException(
                                OPPResources.Error_BusinessCardExchange, ex);
                        }
                        finally
                        {
                            // Release the network stream.
                            Monitor.Exit(this.networkStream);
                        }
                    }
                    break;
            }
        }

        /// <summary>
        /// Event handler for background worker's RunWorkerCompleted event. 
        /// This method packs the outcome of an asynchronous operation into the
        /// appropriate EventArgs and raises the operation's Completed event if
        /// implemented. It then puts the background worker to work on any 
        /// pending asynchronous requests from the request queue.
        /// </summary>
        /// <param name="sender">The background worker that will process this
        /// method.</param>
        /// <param name="e">RunWorkerCompletedEventArgs which stores the outcome
        /// of the operation.</param>
        private void asyncOperationHandler_RunWorkerCompleted(
            object sender, RunWorkerCompletedEventArgs e)
        {
            // If there is no current request, exit.
            if (this.runningAsyncRequest != null)
            {
                // Get the parameters of the request.
                object[] requestParameters =
                    this.runningAsyncRequest.RequestParameters;
                Exception operationException = null;

                // Raise the appropriate completed event.
                switch (this.runningAsyncRequest.RequestType)
                {
                    case OPPRequestType.ObjectPush:
                        if (this.ObjectPushCompleted != null)
                        {
                            if (!e.Cancelled && e.Result != null
                                    && (e.Result is Exception))
                                // If the operation was not cancelled 
                                // and there is some exception.
                                operationException = (Exception)e.Result;

                            // Raise the event.
                            ObjectPushCompleted(this,
                                new ObjectPushCompletedEventArgs(
                                requestParameters[0].ToString(),
                                operationException, e.Cancelled,
                                this.runningAsyncRequest.UserState));
                        }
                        break;

                    case OPPRequestType.BusinessCardPull:
                    case OPPRequestType.BusinessCardPullWithVerification:
                        if (this.BusinessCardPullCompleted != null)
                        {
                            // Get the output parameters of the operation.
                            string cardName = string.Empty;
                            byte[] cardContents = new byte[0];

                            if (!e.Cancelled && e.Result != null)
                            {
                                // If the operation was not cancelled and 
                                // there is some result.
                                if (e.Result is Exception)
                                    // Exception occurred.
                                    operationException = (Exception)e.Result;
                                else
                                {
                                    // Operation was successful.
                                    object[] outputParameters =
                                        e.Result as object[];
                                    cardName =
                                        Convert.ToString(outputParameters[0]);
                                    cardContents =
                                        outputParameters[1] as byte[];
                                }
                            }

                            // Raise the event.
                            BusinessCardPullCompleted(this,
                                    new BusinessCardPullCompletedEventArgs(
                                    cardName, cardContents,
                                    operationException, e.Cancelled,
                                    this.runningAsyncRequest.UserState));
                        }
                        break;

                    case OPPRequestType.BusinessCardExchange:
                    case OPPRequestType.BusinessCardExchangeWithVerification:
                        if (this.BusinessCardExchangeCompleted != null)
                        {
                            // Get the output parameters of the operation.
                            string cardName = string.Empty;
                            byte[] cardContents = new byte[0];

                            if (!e.Cancelled && e.Result != null)
                            {
                                // If the operation was not cancelled and 
                                // there is some result.
                                if (e.Result is Exception)
                                    // Exception occurred.
                                    operationException = (Exception)e.Result;
                                else
                                {
                                    // Operation was successful.
                                    object[] outputParameters =
                                        e.Result as object[];
                                    cardName =
                                        Convert.ToString(outputParameters[0]);
                                    cardContents =
                                        outputParameters[1] as byte[];
                                }
                            }

                            // Raise the event.
                            BusinessCardExchangeCompleted(this,
                                new BusinessCardExchangeCompletedEventArgs(
                                requestParameters[0].ToString(), cardName,
                                cardContents, operationException, e.Cancelled,
                                this.runningAsyncRequest.UserState));
                        }
                        break;
                }
            }

            // If there are pending asynchronous requests, set the background
            // worker to work on them.
            if (this.requestQueue.Count > 0)
                this.asyncOperationHandler.RunWorkerAsync();
        }

        /// <summary>
        /// Pushes data over the stream to the remote OPP Service.
        /// </summary>
        /// <param name="localObjectName">Name of the object to be pushed to
        /// the remote device.</param>
        /// <param name="localObjectContents">The object to be pushed.</param>
        /// <exception cref="ObjectExchangeException">The remote service denies
        /// the push request or an error occurs during the push operation or an
        /// attempt to access the network stream fails.</exception>
        /// <exception cref="InvalidOperationException">networkStream is not set.</exception>
        private void PushObjectToOPPService(
            string localObjectName, byte[] localObjectContents)
        {
            if (this.networkStream == null)
                throw new InvalidOperationException(OPPResources.Error_NetworkStreamNotSet);

            MemoryStream localObjectStream =
                new MemoryStream(localObjectContents);
            BinaryReader objectReader = new BinaryReader(localObjectStream);

            #region Create the Put request(s)

            // List of Put requests to be sent.
            List<ObjectExchangeItem> putRequestPackets =
                new List<ObjectExchangeItem>();

            ObjectExchangeItem packet =
                new ObjectExchangeItem(ObjectExchangeOpcode.Put);
            // Add the Name and Length headers to the 1st Put packet.
            packet.AddHeader(ObjectExchangeHeaderType.Name, localObjectName);
            packet.AddHeader(ObjectExchangeHeaderType.Length,
                             BitConverter.GetBytes(
                             ObjectExchangeHelper.FormatUInt32(
                             Convert.ToUInt32(
                             localObjectContents.Length))));

            long noOfObjectBytesLeftToRead = localObjectStream.Length;
            // If (packet size + number of bytes of the object left to
            // read + 3 bytes for the Body header (1 byte headerId + 2 byte
            // length) is greater than the max packet size that can be 
            // sent), spread the object across multiple packets else send
            // it in a single packet.
            if ((packet.PacketSize + noOfObjectBytesLeftToRead + 3)
                > maxPacketSize)
            {
                // Object is large. So split the Object into packets.

                // 1st packet.
                int noOfObjectBytesToRead =
                    maxPacketSize - packet.PacketSize - 3;
                packet.AddHeader(ObjectExchangeHeaderType.Body,
                    objectReader.ReadBytes(noOfObjectBytesToRead));
                noOfObjectBytesLeftToRead -= noOfObjectBytesToRead;
                putRequestPackets.Add(packet);

                // Rest of the packets.
                while (noOfObjectBytesLeftToRead > 0)
                {
                    packet = new ObjectExchangeItem(ObjectExchangeOpcode.Put);
                    if (noOfObjectBytesLeftToRead >
                        (maxPacketSize - packet.PacketSize - 3))
                    {
                        // Intermediate packet.
                        noOfObjectBytesToRead =
                            maxPacketSize - packet.PacketSize - 3;
                        packet.AddHeader(ObjectExchangeHeaderType.Body,
                            objectReader.ReadBytes(noOfObjectBytesToRead));
                    }
                    else
                    {
                        //Last packet.
                        packet.Opcode = ObjectExchangeOpcode.PutFinal;
                        noOfObjectBytesToRead =
                            (int)noOfObjectBytesLeftToRead;
                        packet.AddHeader(ObjectExchangeHeaderType.EndOfBody,
                            objectReader.ReadBytes(noOfObjectBytesToRead));
                    }
                    noOfObjectBytesLeftToRead -= noOfObjectBytesToRead;
                    putRequestPackets.Add(packet);
                }
            }
            else
            {
                // Object fits in 1 packet.
                packet.Opcode = ObjectExchangeOpcode.PutFinal;
                packet.AddHeader(ObjectExchangeHeaderType.EndOfBody,
                    objectReader.ReadBytes((int)noOfObjectBytesLeftToRead));
                putRequestPackets.Add(packet);
            }

            #endregion

            #region Send the Put request(s)

            for (int packetNo = 0;
                packetNo < putRequestPackets.Count; packetNo++)
            {
                byte[] putRequestBytes =
                    putRequestPackets[packetNo].GetByteStream();
                MemoryStream putResponseStream = new MemoryStream();

                try
                {
                    // Send the Put request.
                    this.networkStream.Write(
                        putRequestBytes, 0, putRequestBytes.Length);

                    // Wait for a response.
                    while (!this.networkStream.DataAvailable)
                        continue;

                    // Wait for a second to give server time to put all the
                    // data onto the network stream.
                    Thread.Sleep(1000);

                    // Read the response.
                    while (this.networkStream.DataAvailable)
                        putResponseStream.WriteByte(
                            (byte)this.networkStream.ReadByte());
                    putResponseStream.Seek(0, SeekOrigin.Begin);
                }
                catch (Exception ex)
                {
                    throw new ObjectExchangeException(
                        ObjectExchangeErrorType.StreamError,
                        OPPResources.Error_NetworkStream, ex);
                }

                ObjectExchangeItem putResponse =
                    new ObjectExchangeItem(putResponseStream);

                // Take action based on the response sent by the Object 
                // Push Service.
                if (putResponse.Opcode != ObjectExchangeOpcode.Success &&
                  putResponse.Opcode != ObjectExchangeOpcode.SuccessFinal &&
                  putResponse.Opcode != ObjectExchangeOpcode.Continue &&
                  putResponse.Opcode != ObjectExchangeOpcode.ContinueFinal)
                    // If response code is not Success or Continue, raise an 
                    // error.
                    ObjectExchangeHelper.RaiseObjectExchangeError(
                        putResponse.Opcode);
            }
            #endregion
        }

        /// <summary>
        /// Pulls the default business card of an OPP Service. 
        /// </summary>
        /// <param name="remoteCardName">Name of the business card received.
        /// </param>
        /// <param name="remoteCardContents">Byte stream of the business card
        /// received.</param>
        /// <param name="typeExpected">Should be of type Vcard as specified in 
        /// the Type Verifier. Should be null if type does not have to be 
        /// verified. </param>
        /// <exception cref="ObjectExchangeException">The remote service denies
        /// the pull request or an error occurs during the pull operation or an
        /// attempt to access the network stream fails.</exception>
        /// <exception cref="InvalidOperationException">networkStream is not set.</exception>
        private void PullBusinessCardFromOPPService(out string remoteCardName,
            out byte[] remoteCardContents, string typeExpected)
        {
            if (this.networkStream == null)
                throw new InvalidOperationException(OPPResources.Error_NetworkStreamNotSet);

            remoteCardName = string.Empty;
            remoteCardContents = new byte[] { };
            uint receivedCardLength = 0;
            MemoryStream remoteCardStream = new MemoryStream();
            BinaryWriter cardWriter = new BinaryWriter(remoteCardStream);
            bool continueGet = false;

            do
            {
                // Create the Get Default Object request.
                ObjectExchangeItem getRequest =
                    new ObjectExchangeItem(ObjectExchangeOpcode.GetFinal);
                if (!continueGet)
                {
                    getRequest.AddHeader(ObjectExchangeHeaderType.Name,
                        string.Empty);
                    getRequest.AddHeader(ObjectExchangeHeaderType.Type,
                        OPPResources.BusinessCardType);
                }
                byte[] getRequestBytes = getRequest.GetByteStream();

                MemoryStream getResponseStream = new MemoryStream();

                try
                {
                    // Send the Get request.
                    this.networkStream.Write(
                        getRequestBytes, 0, getRequestBytes.Length);

                    // Wait for a response.
                    while (!this.networkStream.DataAvailable)
                        continue;

                    // Wait for a second to give server time to put all the
                    // data onto the network stream.
                    Thread.Sleep(1000);

                    // Read the response.
                    while (this.networkStream.DataAvailable)
                        getResponseStream.WriteByte(
                            (byte)this.networkStream.ReadByte());
                    getResponseStream.Seek(0, SeekOrigin.Begin);
                }
                catch (Exception ex)
                {
                    throw new ObjectExchangeException(
                        ObjectExchangeErrorType.StreamError,
                        OPPResources.Error_NetworkStream, ex);
                }

                ObjectExchangeItem getResponse =
                    new ObjectExchangeItem(getResponseStream);

                // Take action based on the response sent by the Object
                // Push Service.
                switch (getResponse.Opcode)
                {
                    // Get successful. More packets coming.
                    case ObjectExchangeOpcode.Continue:
                    case ObjectExchangeOpcode.ContinueFinal:
                        // Get the name of the business card.
                        if (getResponse.HeaderTypeExists(
                                ObjectExchangeHeaderType.Name))
                            remoteCardName =
                                Encoding.BigEndianUnicode.GetString(
                                getResponse.GetHeader(
                                ObjectExchangeHeaderType.Name));

                        // Get the length of the business card.
                        if (getResponse.HeaderTypeExists(
                                ObjectExchangeHeaderType.Length))
                            receivedCardLength =
                                ObjectExchangeHelper.FormatUInt32(
                                    BitConverter.ToUInt32(
                                    getResponse.GetHeader(
                                    ObjectExchangeHeaderType.Length), 0));

                        // Get the contents of the business card.
                        if (getResponse.HeaderTypeExists(
                            ObjectExchangeHeaderType.Body))
                            cardWriter.Write(getResponse.GetHeader(
                                ObjectExchangeHeaderType.Body));
                        continueGet = true;
                        break;

                    // Get successful. Last packet.
                    case ObjectExchangeOpcode.Success:
                    case ObjectExchangeOpcode.SuccessFinal:
                        // Get the name of the business card.
                        if (getResponse.HeaderTypeExists(
                                ObjectExchangeHeaderType.Name))
                            remoteCardName =
                                Encoding.BigEndianUnicode.GetString(
                                getResponse.GetHeader(
                                ObjectExchangeHeaderType.Name));

                        // Get the length of the business card.
                        if (getResponse.HeaderTypeExists(
                                ObjectExchangeHeaderType.Length))
                            receivedCardLength =
                                ObjectExchangeHelper.FormatUInt32(
                                    BitConverter.ToUInt32(
                                    getResponse.GetHeader(
                                    ObjectExchangeHeaderType.Length), 0));

                        // Get the contents of the business card.
                        if (getResponse.HeaderTypeExists(
                            ObjectExchangeHeaderType.EndOfBody))
                            cardWriter.Write(getResponse.GetHeader(
                                ObjectExchangeHeaderType.EndOfBody));

                        // Get the card bytes.
                        remoteCardContents = remoteCardStream.ToArray();

                        if (receivedCardLength > 0 &&
                            remoteCardContents.Length != receivedCardLength)
                            throw new ObjectExchangeException(
                                ObjectExchangeErrorType.StreamError,
                                OPPResources.Error_FaultyBusinessCard);
                        continueGet = false;
                        break;

                    default:
                        ObjectExchangeHelper.RaiseObjectExchangeError(
                            getResponse.Opcode);
                        break;
                }
            }
            while (continueGet);

            cardWriter.Close();

            if (!string.IsNullOrEmpty(typeExpected))
            {
                // Type has to be verified. Verify the card.
                typeVerifier.VerifyType(remoteCardContents, typeExpected);
            }
        }

        /// <summary>
        /// Pushes a business card and pulls the default business card of an 
        /// OPP Service. 
        /// </summary>
        /// <param name="localCardName">Name of the business card to send to 
        /// the OPP Service.</param>
        /// <param name="localCardContents">Byte stream of the business card to
        /// send to the OPP Service.</param>
        /// <param name="remoteCardName">Name of the business card sent by the
        /// OPP Service.</param>
        /// <param name="remoteCardContents">Byte stream of the business card
        /// sent by the OPP Service.</param>
        /// <param name="typeExpected">Should be of type Vcard as specified in 
        /// the Type Verifier. Should be null if type does not have to be 
        /// verified. </param>
        /// <exception cref="ObjectExchangeException">The remote service denies
        /// the exchange request or an error occurs during the exchange 
        /// operation or an attempt to access the network stream fails.
        /// </exception>
        /// <exception cref="InvalidOperationException">networkStream is not set.</exception>
        private void ExchangeBusinessCardsWithOPPService(string localCardName,
            byte[] localCardContents, out string remoteCardName,
            out byte[] remoteCardContents, string typeExpected)
        {
            // Push the object to the remote device.
            PushObjectToOPPService(localCardName, localCardContents);
            // Pull the default business card of the remote device.
            PullBusinessCardFromOPPService(out remoteCardName,
                out remoteCardContents, typeExpected);
        }


        #endregion

        #region Public Methods/Functions

        /// <summary>
        /// Sends a Connect request to an OPP service.
        /// </summary>
        /// <exception cref="ObjectExchangeException">The remote service denies
        /// the connect request or an error occurs during the connect operation
        /// or an attempt to access the network stream fails.</exception>
        /// <exception cref="InvalidOperationException">networkStream is not set.</exception>
        public void Connect()
        {
            if (this.networkStream == null)
                throw new InvalidOperationException(OPPResources.Error_NetworkStreamNotSet);

            // Create the Connect request.
            ObjectExchangeItem connectRequest =
                new ObjectExchangeItem(ObjectExchangeOpcode.Connect);

            // Add an Obex Version Number field to the packet.
            connectRequest.AddHeader(
                ObjectExchangeHeaderType.ObexVersionNumber,
                new byte[] { 0x10 });

            // Add a Flags field to the packet.
            connectRequest.AddHeader(ObjectExchangeHeaderType.Flags,
                new byte[] { 0x00 });

            // Add a Maximum Obex Packet Length field to the packet.
            connectRequest.AddHeader(
                ObjectExchangeHeaderType.MaxObexPacketLength,
                new byte[] { 0x20, 0x00 });

            byte[] connectRequestBytes = connectRequest.GetByteStream();

            MemoryStream connectResponseStream = new MemoryStream();
            try
            {
                // Lock the network stream.
                Monitor.Enter(this.networkStream);

                // Send the Connect request.
                this.networkStream.Write(
                    connectRequestBytes, 0, connectRequestBytes.Length);

                // Wait for a response.
                while (!this.networkStream.DataAvailable)
                    continue;

                // Wait for a second to give server time to put all the data
                // onto the network stream.
                Thread.Sleep(1000);

                // Read the response.
                while (this.networkStream.DataAvailable)
                    connectResponseStream.WriteByte(
                        (byte)this.networkStream.ReadByte());
                connectResponseStream.Seek(0, SeekOrigin.Begin);
            }
            catch (Exception ex)
            {
                throw new ObjectExchangeException(
                    ObjectExchangeErrorType.StreamError,
                    OPPResources.Error_NetworkStream, ex);
            }
            finally
            {
                // Release the network stream.
                Monitor.Exit(this.networkStream);
            }

            ObjectExchangeItem connectResponse =
                new ObjectExchangeItem(connectResponseStream);

            // Take action based on the response sent by the Object Push 
            // Service.
            if (connectResponse.Opcode == ObjectExchangeOpcode.Success ||
                connectResponse.Opcode == ObjectExchangeOpcode.SuccessFinal)
            {
                // Connection successful.
                // Decide the maximum length of an object exchange packet.
                ushort remoteServiceMaxPacketSize =
                        ObjectExchangeHelper.FormatUInt16(
                        BitConverter.ToUInt16(connectResponse.GetHeader(
                        ObjectExchangeHeaderType.MaxObexPacketLength), 0));
                if (DEFAULT_MAX_PACKET_SIZE > remoteServiceMaxPacketSize)
                    this.maxPacketSize = remoteServiceMaxPacketSize;
            }
            else
                ObjectExchangeHelper.RaiseObjectExchangeError(
                    connectResponse.Opcode);
        }

        /// <summary>
        /// Sends a Disconnect request to an OPP service.
        /// </summary>
        /// <exception cref="ObjectExchangeException">The remote service denies
        /// the disconnect request or an error occurs during the disconnect 
        /// operation or an attempt to access the network stream fails.
        /// </exception>
        /// <exception cref="InvalidOperationException">networkStream is not set.</exception>
        public void Disconnect()
        {
            if (this.networkStream == null)
                throw new InvalidOperationException(OPPResources.Error_NetworkStreamNotSet);

            // Create the Disconnect request.
            ObjectExchangeItem disconnectRequest =
                new ObjectExchangeItem(ObjectExchangeOpcode.Disconnect);
            byte[] disconnectRequestBytes = disconnectRequest.GetByteStream();
            MemoryStream disconnectResponseStream = new MemoryStream();

            try
            {
                // Lock the network stream.
                Monitor.Enter(this.networkStream);

                // Send the Disconnect request.
                this.networkStream.Write(
                    disconnectRequestBytes, 0, disconnectRequestBytes.Length);

                // Wait for a response.
                while (!this.networkStream.DataAvailable)
                    continue;

                // Wait for a second to give server time to put all the data 
                // onto the network stream.
                Thread.Sleep(1000);

                // Read the response.
                while (this.networkStream.DataAvailable)
                    disconnectResponseStream.WriteByte(
                        (byte)this.networkStream.ReadByte());
                disconnectResponseStream.Seek(0, SeekOrigin.Begin);
            }
            catch (Exception ex)
            {
                throw new ObjectExchangeException(
                    ObjectExchangeErrorType.StreamError,
                    OPPResources.Error_NetworkStream, ex);
            }
            finally
            {
                // Release the network stream.
                Monitor.Exit(this.networkStream);
            }

            ObjectExchangeItem disconnectResponse =
                new ObjectExchangeItem(disconnectResponseStream);

            // Take action based on the response sent by the Object Push 
            // Service.
            if (disconnectResponse.Opcode != ObjectExchangeOpcode.Success &&
                disconnectResponse.Opcode != ObjectExchangeOpcode.SuccessFinal)
                ObjectExchangeHelper.RaiseObjectExchangeError(
                    disconnectResponse.Opcode);
        }

        /// <summary>
        /// Synchronous method that pushes a file over the stream to the remote
        /// OPP Service.
        /// </summary>
        /// <param name="localFilePath">Path of the file to be pushed.</param>
        /// <exception cref="ArgumentNullException">localFilePath is passed as
        /// an empty string or a null reference.</exception>
        /// <exception cref="ArgumentException">localFilePath specified does
        /// not exist.</exception>
        /// <exception cref="OPPException">An error occurs during the push
        /// operation. (Note that the inner exception contains details of the
        /// reason for exception)</exception>
        public void PushObject(string localFilePath)
        {
            if (string.IsNullOrEmpty(localFilePath))
                throw new ArgumentNullException("localFilePath");

            if (!File.Exists(localFilePath))
                throw new ArgumentException(OPPResources.Error_FileDoesntExist,
                    "localFilePath");

            PushObject(Path.GetFileName(localFilePath),
                File.ReadAllBytes(localFilePath));
        }

        /// <summary>
        /// Synchronous method that pushes data over the stream to the remote 
        /// OPP Service.
        /// </summary>
        /// <param name="localObjectName">Name of the object to be pushed.
        /// </param>
        /// <param name="localObjectContent">The object to be pushed.</param>
        /// <exception cref="ArgumentNullException">localObjectName is passed
        /// as an empty string or a null reference or localObjectContent is
        /// passed as a null reference.</exception>
        /// <exception cref="OPPException">An error occurs during the push
        /// operation. (Note that the inner exception contains details of the
        /// reason for exception)</exception>
        public void PushObject(
            string localObjectName, byte[] localObjectContent)
        {
            if (string.IsNullOrEmpty(localObjectName))
                throw new ArgumentNullException("localObjectName");

            if (localObjectContent == null)
                throw new ArgumentNullException("localObjectContent");

            // Raise the ObjectPushBegin event. 
            if (ObjectPushBegin != null)
                ObjectPushBegin(this, new ObjectPushBeginEventArgs(
                    localObjectName, null));

            Exception exception = null;

            try
            {
                // Lock the network stream.
                Monitor.Enter(this.networkStream);

                // Push the object to the remote device.
                PushObjectToOPPService(localObjectName, localObjectContent);
            }
            catch (Exception ex)
            {
                exception = new OPPException(OPPResources.Error_ObjectPush, ex);
                throw exception;
            }
            finally
            {
                // Release the network stream.
                Monitor.Exit(this.networkStream);

                // Raise the ObjectPushCompleted event. 
                if (ObjectPushCompleted != null)
                    ObjectPushCompleted(this, new ObjectPushCompletedEventArgs(
                        localObjectName, exception, false, null));
            }
        }

        /// <summary>
        /// Asynchronous method that pushes a file over the stream to the remote
        /// OPP Service.
        /// </summary>
        /// <param name="localFilePath">Path of the file to be pushed.</param>
        /// <param name="userState">A unique object which can be used to keep 
        /// track of the request.</param>
        /// <exception cref="ArgumentNullException">localFilePath is passed as
        /// an empty string or a null reference.</exception>
        /// <exception cref="ArgumentException">localFilePath specified does
        /// not exist.</exception>
        public void PushObjectAsync(string localFilePath, object userState)
        {
            if (string.IsNullOrEmpty(localFilePath))
                throw new ArgumentNullException("localFilePath");

            if (!File.Exists(localFilePath))
                throw new ArgumentException(OPPResources.Error_FileDoesntExist,
                    "localFilePath");

            // Add the request to the queue.
            AddRequestToRequestQueue(new OPPRequest(OPPRequestType.ObjectPush,
                userState, Path.GetFileName(localFilePath),
                File.ReadAllBytes(localFilePath)));
        }

        /// <summary>
        /// Asynchronous method that pushes data over the stream to the remote
        /// OPP Service.
        /// </summary>
        /// <param name="localObjectName">Name of the object to be pushed.
        /// </param>
        /// <param name="localObjectContent">The object to be pushed.</param>
        /// <param name="userState">A unique object which can be used to keep 
        /// track of the request.</param>
        /// <exception cref="ArgumentNullException">localObjectName is passed
        /// as an empty string or a null reference or localObjectContent is
        /// passed as a null reference.</exception>
        public void PushObjectAsync(
            string localObjectName, byte[] localObjectContent, object userState)
        {
            if (string.IsNullOrEmpty(localObjectName))
                throw new ArgumentNullException("localObjectName");

            if (localObjectContent == null)
                throw new ArgumentNullException("localObjectContent");

            // Add the request to the queue.
            AddRequestToRequestQueue(new OPPRequest(OPPRequestType.ObjectPush,
                userState, localObjectName, localObjectContent));
        }

        /// <summary>
        /// Synchronous method that pulls the default business card of an OPP 
        /// Service. 
        /// </summary>
        /// <param name="remoteCardName">Name of the business card received.
        /// </param>
        /// <param name="remoteCardContents">Byte stream of the business card
        /// received.</param>
        /// <exception cref="OPPException">An error occurs during the pull
        /// operation. (Note that the inner exception contains details of the
        /// reason for exception)</exception>
        public void PullBusinessCard(
            out string remoteCardName, out byte[] remoteCardContents)
        {
            // Raise the BusinessCardPullBegin event. 
            if (BusinessCardPullBegin != null)
                BusinessCardPullBegin(this,
                    new BusinessCardPullBeginEventArgs(null));

            Exception exception = null;
            remoteCardName = string.Empty;
            remoteCardContents = new byte[0];

            try
            {
                // Lock the network stream.
                Monitor.Enter(this.networkStream);

                // Pull the default business card from the remote device.
                PullBusinessCardFromOPPService(
                    out remoteCardName, out remoteCardContents, null);
            }
            catch (Exception ex)
            {
                exception = new OPPException(OPPResources.Error_BusinessCardPull, ex);
                throw exception;
            }
            finally
            {
                // Release the network stream.
                Monitor.Exit(this.networkStream);

                // Raise the BusinessCardPullCompleted event. 
                if (BusinessCardPullCompleted != null)
                    BusinessCardPullCompleted(this,
                        new BusinessCardPullCompletedEventArgs(
                        remoteCardName, remoteCardContents, exception, false, null));
            }
        }

        /// <summary>
        /// Synchronous method that pulls the default business card of an OPP
        /// Service and verifies that the card received is a valid card. Note
        /// that the Type Verifier should be set. If a type mismatch occurs, 
        /// OPPTypeMismatchException is thrown.
        /// </summary>
        /// <param name="remoteCardName">Name of the business card received.
        /// </param>
        /// <param name="remoteCardContents">Byte stream of the business card
        /// received.</param>
        /// <param name="typeExpected">Should be of type Vcard as specified in 
        /// the Type Verifier.</param>
        /// <exception cref="ArgumentNullException">typeExpected is passed as 
        /// an empty string or a null reference.</exception>
        /// <exception cref="InvalidOperationException">TypeVerifier has not
        /// been set.</exception>
        /// <exception cref="ArgumentException">Type verification for 
        /// typeExpected is not supported by the TypeVerifier.</exception>
        /// <exception cref="OPPException">An error occurs during the pull
        /// operation. (Note that the inner exception contains details of the
        /// reason for exception)</exception>
        public void PullBusinessCard(out string remoteCardName,
            out byte[] remoteCardContents, string typeExpected)
        {
            if (string.IsNullOrEmpty(typeExpected))
                throw new ArgumentNullException("typeExpected");

            if (typeVerifier == null)
                throw new InvalidOperationException(
                    OPPResources.Error_VerifierNotSet);

            if (!typeVerifier.IsObjectTypeSupported(typeExpected))
                throw new ArgumentException(
                    OPPResources.Error_TypeNotSupported,
                    "typeExpected");

            // Raise the BusinessCardPullBegin event. 
            if (BusinessCardPullBegin != null)
                BusinessCardPullBegin(this,
                    new BusinessCardPullBeginEventArgs(null));

            Exception exception = null;
            remoteCardName = string.Empty;
            remoteCardContents = new byte[0];

            try
            {
                // Lock the network stream.
                Monitor.Enter(this.networkStream);

                // Pull the default business card from the remote device.
                PullBusinessCardFromOPPService(
                    out remoteCardName, out remoteCardContents, typeExpected);
            }
            catch (Exception ex)
            {
                exception = new OPPException(OPPResources.Error_BusinessCardPull, ex);
                throw exception;
            }
            finally
            {
                // Release the network stream.
                Monitor.Exit(this.networkStream);

                // Raise the BusinessCardPullCompleted event. 
                if (BusinessCardPullCompleted != null)
                    BusinessCardPullCompleted(this,
                        new BusinessCardPullCompletedEventArgs(remoteCardName,
                        remoteCardContents, exception, false, null));
            }
        }

        /// <summary>
        /// Asynchronous method that pulls the default business card of an OPP
        /// Service.
        /// </summary>
        /// <param name="userState">A unique object which can be used to keep
        /// track of the request.</param>
        public void PullBusinessCardAsync(object userState)
        {
            // Add the request to the queue.
            AddRequestToRequestQueue(new OPPRequest(
                OPPRequestType.BusinessCardPull, userState, string.Empty));
        }

        /// <summary>
        /// Asynchronous method that pulls the default business card of an OPP
        /// Service and verifies that the card received is a valid card. Note
        /// that the Type Verifier should be set. If a type mismatch occurs,
        /// OPPTypeMismatchException is thrown.
        /// </summary>
        /// <param name="userState">A unique object which can be used to keep 
        /// track of the request.</param>
        /// <param name="typeExpected">Should be of type Vcard as specified in
        /// the Type Verifier.</param>
        /// <exception cref="ArgumentNullException">typeExpected is passed as 
        /// an empty string or a null reference.</exception>
        /// <exception cref="InvalidOperationException">TypeVerifier has not
        /// been set.</exception>
        /// <exception cref="ArgumentException">Type verification for 
        /// typeExpected is not supported by the TypeVerifier.</exception>
        public void PullBusinessCardAsync(object userState, string typeExpected)
        {
            if (string.IsNullOrEmpty(typeExpected))
                throw new ArgumentNullException("typeExpected");

            if (typeVerifier == null)
                throw new InvalidOperationException(
                    OPPResources.Error_VerifierNotSet);

            if (!typeVerifier.IsObjectTypeSupported(typeExpected))
                throw new ArgumentException(
                    OPPResources.Error_TypeNotSupported, "typeExpected");

            // Add the request to the queue.
            AddRequestToRequestQueue(new OPPRequest(
                OPPRequestType.BusinessCardPullWithVerification,
                userState, typeExpected));
        }

        /// <summary>
        /// Synchronous method that pushes a business card and pulls the 
        /// default business card of an OPP Service. 
        /// </summary>
        /// <param name="localCardFilePath">Path of the business card file to
        /// be pushed.</param>
        /// <param name="remoteCardName">Name of the business card sent by the 
        /// OPP Service.</param>
        /// <param name="remoteCardContents">Byte stream of the business card
        /// sent by the OPP Service.</param>
        /// <exception cref="ArgumentNullException">localCardFilePath is passed 
        /// as an empty string or a null reference.</exception>
        /// <exception cref="ArgumentException">localCardFilePath specified does
        /// not exist.</exception>
        /// <exception cref="OPPException">An error occurs during the exchange
        /// operation. (Note that the inner exception contains details of the
        /// reason for exception)</exception>
        public void ExchangeBusinessCards(string localCardFilePath,
            out string remoteCardName, out byte[] remoteCardContents)
        {
            if (string.IsNullOrEmpty(localCardFilePath))
                throw new ArgumentNullException("localCardFilePath");

            if (!File.Exists(localCardFilePath))
                throw new ArgumentException(
                    OPPResources.Error_FileDoesntExist, "localCardFilePath");

            ExchangeBusinessCards(Path.GetFileName(localCardFilePath),
                File.ReadAllBytes(localCardFilePath), out remoteCardName,
                out remoteCardContents);
        }

        /// <summary>
        /// Synchronous method that pushes a business card and pulls the default
        /// business card of an OPP Service. 
        /// </summary>
        /// <param name="localCardName">Name of the business card to send to the
        /// OPP Service.</param>
        /// <param name="localCardContents">Byte stream of the business card to 
        /// send to the OPP Service.</param>
        /// <param name="remoteCardName">Name of the business card sent by the 
        /// OPP Service.</param>
        /// <param name="remoteCardContents">Byte stream of the business card 
        /// sent by the OPP Service.</param>
        /// <exception cref="ArgumentNullException">localCardName is passed
        /// as an empty string or a null reference or localCardContents is
        /// passed as a null reference.</exception>
        /// <exception cref="OPPException">An error occurs during the exchange
        /// operation. (Note that the inner exception contains details of the
        /// reason for exception)</exception>
        public void ExchangeBusinessCards(
            string localCardName, byte[] localCardContents,
            out string remoteCardName, out byte[] remoteCardContents)
        {
            if (string.IsNullOrEmpty(localCardName))
                throw new ArgumentNullException("localCardName");

            if (localCardContents == null)
                throw new ArgumentNullException("localCardContents");

            // Raise the BusinessCardExchangeBegin event. 
            if (BusinessCardExchangeBegin != null)
                BusinessCardExchangeBegin(this,
                    new BusinessCardExchangeBeginEventArgs(
                    localCardName, null));

            Exception exception = null;
            remoteCardName = string.Empty;
            remoteCardContents = new byte[0];

            try
            {
                // Lock the network stream.
                Monitor.Enter(this.networkStream);

                // Exchange cards with the remote device.
                ExchangeBusinessCardsWithOPPService(localCardName,
                    localCardContents, out remoteCardName,
                    out remoteCardContents, null);
            }
            catch (Exception ex)
            {
                exception = new OPPException(
                    OPPResources.Error_BusinessCardExchange, ex);
                throw exception;
            }
            finally
            {
                // Release the network stream.
                Monitor.Exit(this.networkStream);

                // Raise the BusinessCardExchangeCompleted event. 
                if (BusinessCardExchangeCompleted != null)
                    BusinessCardExchangeCompleted(this,
                        new BusinessCardExchangeCompletedEventArgs(
                        localCardName, remoteCardName, remoteCardContents,
                        exception, false, null));
            }
        }

        /// <summary>
        /// Synchronous method that pushes a business card and pulls the 
        /// default business card of an OPP Service and verifies that the card
        /// received is a valid card. Note that the Type Verifier should be 
        /// set. If a type mismatch occurs, OPPTypeMismatchException is thrown.
        /// </summary>
        /// <param name="localCardFilePath">Path of the business card file to 
        /// be pushed.</param>
        /// <param name="remoteCardName">Name of the business card sent by the
        /// OPP Service.</param>
        /// <param name="remoteCardContents">Byte stream of the business card 
        /// sent by the OPP Service.</param>
        /// <param name="typeExpected">Should be of type Vcard as specified in 
        /// the Type Verifier.</param>
        /// <exception cref="ArgumentNullException">localCardFilePath or 
        /// typeExpected is passed as an empty string or a null reference.
        /// </exception>
        /// <exception cref="ArgumentException">localCardFilePath specified does
        /// not exist or type verification for typeExpected is not supported by
        /// the TypeVerifier.</exception>
        /// <exception cref="InvalidOperationException">TypeVerifier has not
        /// been set.</exception>
        /// <exception cref="OPPException">An error occurs during the exchange
        /// operation. (Note that the inner exception contains details of the
        /// reason for exception)</exception>
        public void ExchangeBusinessCards(
            string localCardFilePath, out string remoteCardName,
            out byte[] remoteCardContents, string typeExpected)
        {
            if (string.IsNullOrEmpty(localCardFilePath))
                throw new ArgumentNullException("localCardFilePath");

            if (!File.Exists(localCardFilePath))
                throw new ArgumentException(
                    OPPResources.Error_FileDoesntExist, "localCardFilePath");

            ExchangeBusinessCards(Path.GetFileName(localCardFilePath),
                File.ReadAllBytes(localCardFilePath), out remoteCardName,
                out remoteCardContents, typeExpected);
        }

        /// <summary>
        /// Synchronous method that pushes a business card and pulls the default
        /// business card of an OPP Service and verifies that the card received
        /// is a valid card. Note that the Type Verifier should be set. If a 
        /// type mismatch occurs, OPPTypeMismatchException is thrown.
        /// </summary>
        /// <param name="localCardName">Name of the business card to send to the
        /// OPP Service.</param>
        /// <param name="localCardContents">Byte stream of the business card to 
        /// send to the OPP Service.</param>
        /// <param name="remoteCardName">Name of the business card sent by the 
        /// OPP Service.</param>
        /// <param name="remoteCardContents">Byte stream of the business card 
        /// sent by the OPP Service.</param>
        /// <param name="typeExpected">Should be of type Vcard as specified in 
        /// the Type Verifier.</param>
        /// <exception cref="ArgumentNullException">localCardName or 
        /// typeExpected is passed as an empty string or a null reference or
        /// localCardContents is passed as a null reference.</exception>
        /// <exception cref="InvalidOperationException">TypeVerifier has not
        /// been set.</exception>
        /// <exception cref="ArgumentException">Type verification for 
        /// typeExpected is not supported by the TypeVerifier.</exception>
        /// <exception cref="OPPException">An error occurs during the exchange
        /// operation. (Note that the inner exception contains details of the
        /// reason for exception)</exception>
        public void ExchangeBusinessCards(string localCardName,
            byte[] localCardContents, out string remoteCardName,
            out byte[] remoteCardContents, string typeExpected)
        {
            if (string.IsNullOrEmpty(localCardName))
                throw new ArgumentNullException("localCardName");

            if (localCardContents == null)
                throw new ArgumentNullException("localCardContents");

            if (string.IsNullOrEmpty(typeExpected))
                throw new ArgumentNullException("typeExpected");

            if (typeVerifier == null)
                throw new InvalidOperationException(
                    OPPResources.Error_VerifierNotSet);

            if (!typeVerifier.IsObjectTypeSupported(typeExpected))
                throw new ArgumentException(
                    OPPResources.Error_TypeNotSupported, "typeExpected");

            // Raise the BusinessCardExchangeBegin event. 
            if (BusinessCardExchangeBegin != null)
                BusinessCardExchangeBegin(this,
                    new BusinessCardExchangeBeginEventArgs(
                    localCardName, null));

            Exception exception = null;
            remoteCardName = string.Empty;
            remoteCardContents = new byte[0];

            try
            {
                // Lock the network stream.
                Monitor.Enter(this.networkStream);

                // Exchange cards with the remote device.
                ExchangeBusinessCardsWithOPPService(localCardName,
                                                    localCardContents,
                                                    out remoteCardName,
                                                    out remoteCardContents,
                                                    typeExpected);
            }
            catch (Exception ex)
            {
                exception = new OPPException(
                    OPPResources.Error_BusinessCardExchange, ex);
                throw exception;
            }
            finally
            {
                // Release the network stream.
                Monitor.Exit(this.networkStream);

                // Raise the BusinessCardExchangeCompleted event. 
                if (BusinessCardExchangeCompleted != null)
                    BusinessCardExchangeCompleted(this,
                        new BusinessCardExchangeCompletedEventArgs(
                        localCardName, remoteCardName, remoteCardContents,
                        exception, false, null));
            }
        }

        /// <summary>
        /// Asynchronous method that pushes a business card and pulls the 
        /// default business card of an OPP Service. 
        /// </summary>
        /// <param name="localCardFilePath">Path of the business card file to
        /// be pushed.</param>
        /// <param name="userState">A unique object which can be used to keep 
        /// track of the request.</param>
        /// <exception cref="ArgumentNullException">localCardFilePath is passed
        /// as an empty string or a null reference.</exception>
        /// <exception cref="ArgumentException">localCardFilePath specified does
        /// not exist.</exception>
        public void ExchangeBusinessCardsAsync(
            string localCardFilePath, object userState)
        {
            if (string.IsNullOrEmpty(localCardFilePath))
                throw new ArgumentNullException("localCardFilePath");

            if (!File.Exists(localCardFilePath))
                throw new ArgumentException(
                    OPPResources.Error_FileDoesntExist, "localCardFilePath");

            // Add the request to the queue.
            AddRequestToRequestQueue(new OPPRequest(
                OPPRequestType.BusinessCardExchange, userState,
                Path.GetFileName(localCardFilePath),
                File.ReadAllBytes(localCardFilePath), string.Empty));
        }

        /// <summary>
        /// Asynchronous method that pushes a business card and pulls the 
        /// default business card of an OPP Service. 
        /// </summary>
        /// <param name="localCardName">Name of the business card to send to the
        /// OPP Service.</param>
        /// <param name="localCardContents">Byte stream of the business card to 
        /// send to the OPP Service.</param>
        /// <param name="userState">A unique object which can be used to keep 
        /// track of the request.</param>
        /// <exception cref="ArgumentNullException">localCardName is passed
        /// as an empty string or a null reference or localCardContents is
        /// passed as a null reference.</exception>
        public void ExchangeBusinessCardsAsync(
            string localCardName, byte[] localCardContents, object userState)
        {
            if (string.IsNullOrEmpty(localCardName))
                throw new ArgumentNullException("localCardName");

            if (localCardContents == null)
                throw new ArgumentNullException("localCardContents");

            // Add the request to the queue.
            AddRequestToRequestQueue(new OPPRequest(
                OPPRequestType.BusinessCardExchange, userState,
                localCardName, localCardContents, string.Empty));
        }

        /// <summary>
        /// Asynchronous method that pushes a business card and pulls the 
        /// default business card of an OPP Service and verifies that the card 
        /// received is a valid card. Note that the Type Verifier should be 
        /// set. If a type mismatch occurs, OPPTypeMismatchException is thrown.
        /// </summary>
        /// <param name="localCardFilePath">Path of the business card file to be
        /// pushed.</param>
        /// <param name="typeExpected">Should be of type Vcard as specified in
        /// the Type Verifier.</param>
        /// <param name="userState">A unique object which can be used to keep 
        /// track of the request.</param>
        /// <exception cref="ArgumentNullException">localCardFilePath or 
        /// typeExpected is passed as an empty string or a null reference.
        /// </exception>
        /// <exception cref="ArgumentException">localCardFilePath specified does
        /// not exist or type verification for typeExpected is not supported by
        /// the TypeVerifier.</exception>
        /// <exception cref="InvalidOperationException">TypeVerifier has not
        /// been set.</exception>
        public void ExchangeBusinessCardsAsync(
            string localCardFilePath, string typeExpected, object userState)
        {
            if (string.IsNullOrEmpty(localCardFilePath))
                throw new ArgumentNullException("localCardFilePath");

            if (!File.Exists(localCardFilePath))
                throw new ArgumentException(
                    OPPResources.Error_FileDoesntExist, "localCardFilePath");

            if (string.IsNullOrEmpty(typeExpected))
                throw new ArgumentNullException("typeExpected");

            if (typeVerifier == null)
                throw new InvalidOperationException(
                    OPPResources.Error_VerifierNotSet);

            if (!typeVerifier.IsObjectTypeSupported(typeExpected))
                throw new ArgumentException(
                    OPPResources.Error_TypeNotSupported, "typeExpected");

            // Add the request to the queue.
            AddRequestToRequestQueue(new OPPRequest(
                OPPRequestType.BusinessCardExchangeWithVerification, userState,
                Path.GetFileName(localCardFilePath),
                File.ReadAllBytes(localCardFilePath), typeExpected));
        }

        /// <summary>
        /// Asynchronous method that pushes a business card and pulls the 
        /// default business card of an OPP Service and verifies that the card
        /// received is a valid card. Note that the Type Verifier should be set.
        /// If a type mismatch occurs, OPPTypeMismatchException is thrown.
        /// </summary>
        /// <param name="localCardName">Name of the business card to send to 
        /// the OPP Service.</param>
        /// <param name="localCardContents">Byte stream of the business card
        /// to send to the OPP Service.</param>
        /// <param name="typeExpected">Should be of type Vcard as specified in 
        /// the Type Verifier.</param>
        /// <param name="userState">A unique object which can be used to keep 
        /// track of the request.</param>
        /// <exception cref="ArgumentNullException">localCardName or 
        /// typeExpected is passed as an empty string or a null reference or
        /// localCardContents is passed as a null reference.</exception>
        /// <exception cref="InvalidOperationException">TypeVerifier has not
        /// been set.</exception>
        /// <exception cref="ArgumentException">Type verification for 
        /// typeExpected is not supported by the TypeVerifier.</exception>
        public void ExchangeBusinessCardsAsync(string localCardName,
            byte[] localCardContents, string typeExpected, object userState)
        {
            if (string.IsNullOrEmpty(localCardName))
                throw new ArgumentNullException("localCardName");

            if (localCardContents == null)
                throw new ArgumentNullException("localCardContents");

            if (string.IsNullOrEmpty(typeExpected))
                throw new ArgumentNullException("typeExpected");

            if (typeVerifier == null)
                throw new InvalidOperationException(
                    OPPResources.Error_VerifierNotSet);

            if (!typeVerifier.IsObjectTypeSupported(typeExpected))
                throw new ArgumentException(
                    OPPResources.Error_TypeNotSupported, "typeExpected");

            // Add the request to the queue.
            AddRequestToRequestQueue(new OPPRequest(
                OPPRequestType.BusinessCardExchangeWithVerification, userState,
                localCardName, localCardContents, typeExpected));
        }

        /// <summary>
        /// Cancel an asynchronous operation.
        /// Throws an InvalidOperationException if the userState supplied is
        /// invalid or the operation has already completed.
        /// </summary>
        /// <param name="userState">The user-supplied state object supplied 
        /// when the asynchronous operation was requested.</param>
        /// <exception cref="ArgumentNullException">userState is passed as a
        /// null reference.</exception>
        /// <exception cref="InvalidOperationException">userState is invalid
        /// or the operation has already completed.</exception>
        public void CancelAsync(object userState)
        {
            if (userState == null)
                throw new ArgumentNullException("userState");

            bool invalidCancel = true;

            // Check whether this operation is currently executing.
            if (this.asyncOperationHandler.IsBusy
                && this.runningAsyncRequest != null
                && this.runningAsyncRequest.UserState.Equals(userState))
            {
                this.asyncOperationHandler.CancelAsync();
                invalidCancel = false;
            }
            else
            {
                Monitor.Enter(this.requestQueue);
                // Search for the operation in the queue.
                foreach (OPPRequest request in this.requestQueue)
                {
                    if (request.UserState.Equals(userState))
                    {
                        this.requestQueue.Remove(request);
                        invalidCancel = false;
                        break;
                    }
                }
                Monitor.Exit(this.requestQueue);
            }

            // The userState was not found.
            if (invalidCancel)
                throw new InvalidOperationException(
                    OPPResources.Error_InvalidOrNotFoundUserState);
        }

        #endregion
    }
}